package com.easyaop.test.core.junit;

import com.easyaop.api.EasyAopFactory;
import com.easyaop.api.Events;
import com.easyaop.api.pointcut.MethodPointcut;
import com.easyaop.api.event.BeforeEvent;
import com.easyaop.api.event.ReturnEvent;
import com.easyaop.api.event.ThrowableEvent;
import com.easyaop.api.event.listener.MethodListener;
import com.easyaop.core.EventsImpl;
import net.bytebuddy.jar.asm.Type;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.easyaop.test.core.junit.JunitTest.*;
import static org.mockito.ArgumentCaptor.forClass;
import static org.mockito.Mockito.*;

public class JunitTestForEvent {
    @TestFactory
    Collection<DynamicTest> tests() {
        return Arrays.stream(JunitBean.class.getDeclaredMethods())
                .map(method -> DynamicTest.dynamicTest(method.getName(), () -> testMethod(method))).collect(Collectors.toSet());

    }

    public static void testMethod(Method method) throws Exception {
        System.out.println(method);
        method.setAccessible(true);

        Object[] args = argsForDefault(method);
        Class<?> declaringClass = method.getDeclaringClass();
        Class<?> returnType = method.getReturnType();
        Object thisObj = declaringClass.newInstance();
        String signature = declaringClass.getName() + "#" + method.getName() + Type.getMethodDescriptor(method);

        MethodListener methodListener1 = mock(MethodListener2.class);
        MethodListener methodListener2 = mock(MethodListener3.class);
        Events events = EventsImpl.getInstance();
        events= EasyAopFactory.getEasyAopTemple();

        events.addEventListener(MethodPointcut.of(declaringClass.getName(), method.getName(), Type.getMethodDescriptor(method) + "1222"), methodListener2);
        events.addEventListener(method, methodListener1);


        Object result = null;
        Throwable throwable = null;
        try {
            result = method.invoke(thisObj, args);
        } catch (InvocationTargetException e) {
            throwable = e.getTargetException();
        }

        System.out.println("---before 校验");
        ArgumentCaptor<BeforeEvent> beforeEventArgumentCaptor = forClass(BeforeEvent.class);

        verify(methodListener1, times(1))
                .before(beforeEventArgumentCaptor.capture());
        System.out.println("执行on before 1次");

        BeforeEvent beforeEvent = beforeEventArgumentCaptor.getValue();

        checkTarget(method, thisObj, beforeEvent.getThis());
        System.out.println("this对象正确");

        checkArgs(method, args, beforeEvent.getArguments());
        System.out.println("args正确");

        assert signature.equals(beforeEvent.getSignature());
        System.out.println("签名正确");


        InOrder inOrder = inOrder(methodListener1, methodListener2);
        inOrder.verify(methodListener1, times(1)).before(beforeEvent);
        inOrder.verify(methodListener2, times(1)).before(beforeEvent);

        if (throwable != null) {
            System.out.println("---on 异常 校验");

            ArgumentCaptor<ThrowableEvent> exceptionEventArgumentCaptor = forClass(ThrowableEvent.class);
            verify(methodListener1, times(1))
                    .exception(exceptionEventArgumentCaptor.capture());
            System.out.println("执行 on 异常 1次");

            ThrowableEvent throwableEvent = exceptionEventArgumentCaptor.getValue();

            checkTarget(method, thisObj, throwableEvent.getThis());
            System.out.println("this对象正确");

            checkArgs(method, args, throwableEvent.getArguments());
            System.out.println("args正确");

            assert signature.equals(throwableEvent.getSignature());
            System.out.println("签名正确");

            checkEx(throwable, throwableEvent.getThrowable());
            System.out.println("throwable 正确");

        } else {
            System.out.println("---on return 校验");

            ArgumentCaptor<ReturnEvent> returnEventArgumentCaptor = forClass(ReturnEvent.class);
            verify(methodListener1, times(1))
                    .afterReturn(returnEventArgumentCaptor.capture());
            System.out.println("走了after 1次");

            ReturnEvent returnEvent = returnEventArgumentCaptor.getValue();

            checkTarget(method, thisObj, returnEvent.getThis());
            System.out.println("this正确");

            checkArgs(method, args, returnEvent.getArguments());
            System.out.println("args正确");

            assert signature.equals(returnEvent.getSignature());
            System.out.println("签名正确");

            if (returnType.isPrimitive()) {
                assert Objects.equals(result, returnEvent.getReturnValue());
            } else {
                assert result == returnEvent.getReturnValue();
            }
            System.out.println("返回值正确：");
        }
    }
}
